/* bmc.c */

#include "ptpd.h"

/* Convert EUI48 format to EUI64 */
void EUI48toEUI64(const octet_t * eui48, octet_t * eui64)
{
	eui64[0] = eui48[0];
	eui64[1] = eui48[1];
	eui64[2] = eui48[2];
	eui64[3] = 0xff;
	eui64[4] = 0xfe;
	eui64[5] = eui48[3];
	eui64[6] = eui48[4];
	eui64[7] = eui48[5];
}

/* Init ptpClock with run time values (initialization constants are in constants.h) */
void initData(PtpClock *ptpClock)
{
	RunTimeOpts * rtOpts;

	DBG("initData\n");
	rtOpts = ptpClock->rtOpts;

	/* Default data set */
	ptpClock->defaultDS.twoStepFlag = DEFAULT_TWO_STEP_FLAG;

	/* Init clockIdentity with MAC address and 0xFF and 0xFE. see spec 7.5.2.2.2 */
	if ((CLOCK_IDENTITY_LENGTH == 8) && (PTP_UUID_LENGTH == 6))
	{
			DBGVV("initData: EUI48toEUI64\n");
			EUI48toEUI64(ptpClock->portUuidField, ptpClock->defaultDS.clockIdentity);
	}
	else if (CLOCK_IDENTITY_LENGTH == PTP_UUID_LENGTH)
	{
			memcpy(ptpClock->defaultDS.clockIdentity, ptpClock->portUuidField, CLOCK_IDENTITY_LENGTH);
	}
	else
	{
			ERROR("initData: UUID length is not valid");
	}

	ptpClock->defaultDS.numberPorts = NUMBER_PORTS;

	ptpClock->defaultDS.clockQuality.clockAccuracy = rtOpts->clockQuality.clockAccuracy;
	ptpClock->defaultDS.clockQuality.clockClass = rtOpts->clockQuality.clockClass;
	ptpClock->defaultDS.clockQuality.offsetScaledLogVariance = rtOpts->clockQuality.offsetScaledLogVariance;

	ptpClock->defaultDS.priority1 = rtOpts->priority1;
	ptpClock->defaultDS.priority2 = rtOpts->priority2;

	ptpClock->defaultDS.domainNumber = rtOpts->domainNumber;
	ptpClock->defaultDS.slaveOnly = rtOpts->slaveOnly;

	/* Port configuration data set */

	/* PortIdentity Init (portNumber = 1 for an ardinary clock spec 7.5.2.3)*/
	memcpy(ptpClock->portDS.portIdentity.clockIdentity, ptpClock->defaultDS.clockIdentity, CLOCK_IDENTITY_LENGTH);
	ptpClock->portDS.portIdentity.portNumber = NUMBER_PORTS;
	ptpClock->portDS.logMinDelayReqInterval = DEFAULT_DELAYREQ_INTERVAL;
	ptpClock->portDS.peerMeanPathDelay.seconds = ptpClock->portDS.peerMeanPathDelay.nanoseconds = 0;
	ptpClock->portDS.logAnnounceInterval = rtOpts->announceInterval;
	ptpClock->portDS.announceReceiptTimeout = DEFAULT_ANNOUNCE_RECEIPT_TIMEOUT;
	ptpClock->portDS.logSyncInterval = rtOpts->syncInterval;
	ptpClock->portDS.delayMechanism = rtOpts->delayMechanism;
	ptpClock->portDS.logMinPdelayReqInterval = DEFAULT_PDELAYREQ_INTERVAL;
	ptpClock->portDS.versionNumber = VERSION_PTP;

	/* Init other stuff */
	ptpClock->foreignMasterDS.count = 0;
	ptpClock->foreignMasterDS.capacity = rtOpts->maxForeignRecords;

	ptpClock->inboundLatency = rtOpts->inboundLatency;
	ptpClock->outboundLatency = rtOpts->outboundLatency;

	ptpClock->servo.sDelay = rtOpts->servo.sDelay;
	ptpClock->servo.sOffset = rtOpts->servo.sOffset;
	ptpClock->servo.ai = rtOpts->servo.ai;
	ptpClock->servo.ap = rtOpts->servo.ap;
	ptpClock->servo.noAdjust = rtOpts->servo.noAdjust;
	ptpClock->servo.noResetClock = rtOpts->servo.noResetClock;

	ptpClock->stats = rtOpts->stats;
}

bool isSamePortIdentity(const PortIdentity * A, const PortIdentity * B)
{
	return (bool)(0 == memcmp(A->clockIdentity, B->clockIdentity, CLOCK_IDENTITY_LENGTH) && (A->portNumber == B->portNumber));
}

void addForeign(PtpClock *ptpClock, const MsgHeader *header, const MsgAnnounce * announce)
{
	int i, j;
	bool found = FALSE;

	j = ptpClock->foreignMasterDS.best;

	/* Check if Foreign master is already known */
	for (i = 0; i < ptpClock->foreignMasterDS.count; i++)
	{
		if (isSamePortIdentity(&header->sourcePortIdentity, &ptpClock->foreignMasterDS.records[j].foreignMasterPortIdentity))
		{
			/* Foreign Master is already in Foreignmaster data set */
			ptpClock->foreignMasterDS.records[j].foreignMasterAnnounceMessages++;
			found = TRUE;
			DBGV("addForeign: AnnounceMessage incremented \n");
			ptpClock->foreignMasterDS.records[j].header = *header;
			ptpClock->foreignMasterDS.records[j].announce = *announce;
			break;
		}

		j = (j + 1) % ptpClock->foreignMasterDS.count;
	}

	/* New Foreign Master */
	if (!found)
	{
		if (ptpClock->foreignMasterDS.count < ptpClock->foreignMasterDS.capacity)
		{
			ptpClock->foreignMasterDS.count++;
		}

		j = ptpClock->foreignMasterDS.i;

		/* Copy new foreign master data set from Announce message */
		memcpy(ptpClock->foreignMasterDS.records[j].foreignMasterPortIdentity.clockIdentity, header->sourcePortIdentity.clockIdentity, CLOCK_IDENTITY_LENGTH);
		ptpClock->foreignMasterDS.records[j].foreignMasterPortIdentity.portNumber = header->sourcePortIdentity.portNumber;
		ptpClock->foreignMasterDS.records[j].foreignMasterAnnounceMessages = 0;

		/* Header and announce field of each Foreign Master are usefull to run Best Master Clock Algorithm */
		ptpClock->foreignMasterDS.records[j].header = *header;
		ptpClock->foreignMasterDS.records[j].announce = *announce;
		DBGV("addForeign: New foreign Master added \n");

		ptpClock->foreignMasterDS.i = (ptpClock->foreignMasterDS.i + 1) % ptpClock->foreignMasterDS.capacity;
	}
}

#define m2 m1

/* Local clock is becoming Master. Table 13 (9.3.5) of the spec.*/
void m1(PtpClock *ptpClock)
{
	DBGV("bmc: m1\n");

	/* Current data set update */
	ptpClock->currentDS.stepsRemoved = 0;
	ptpClock->currentDS.offsetFromMaster.seconds = ptpClock->currentDS.offsetFromMaster.nanoseconds = 0;
	ptpClock->currentDS.meanPathDelay.seconds = ptpClock->currentDS.meanPathDelay.nanoseconds = 0;

	/* Parent data set */
	memcpy(ptpClock->parentDS.parentPortIdentity.clockIdentity, ptpClock->defaultDS.clockIdentity, CLOCK_IDENTITY_LENGTH);
	ptpClock->parentDS.parentPortIdentity.portNumber = 0;
	memcpy(ptpClock->parentDS.grandmasterIdentity, ptpClock->defaultDS.clockIdentity, CLOCK_IDENTITY_LENGTH);
	ptpClock->parentDS.grandmasterClockQuality.clockAccuracy = ptpClock->defaultDS.clockQuality.clockAccuracy;
	ptpClock->parentDS.grandmasterClockQuality.clockClass = ptpClock->defaultDS.clockQuality.clockClass;
	ptpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance = ptpClock->defaultDS.clockQuality.offsetScaledLogVariance;
	ptpClock->parentDS.grandmasterPriority1 = ptpClock->defaultDS.priority1;
	ptpClock->parentDS.grandmasterPriority2 = ptpClock->defaultDS.priority2;

	/* Time Properties data set */
	ptpClock->timePropertiesDS.currentUtcOffset = ptpClock->rtOpts->currentUtcOffset;
	ptpClock->timePropertiesDS.currentUtcOffsetValid = DEFAULT_UTC_VALID;
	ptpClock->timePropertiesDS.leap59 = FALSE;
	ptpClock->timePropertiesDS.leap61 = FALSE;
	ptpClock->timePropertiesDS.timeTraceable = DEFAULT_TIME_TRACEABLE;
	ptpClock->timePropertiesDS.frequencyTraceable = DEFAULT_FREQUENCY_TRACEABLE;
	ptpClock->timePropertiesDS.ptpTimescale = (bool)(DEFAULT_TIMESCALE == PTP_TIMESCALE);
	ptpClock->timePropertiesDS.timeSource = DEFAULT_TIME_SOURCE;
}

void p1(PtpClock *ptpClock)
{
	DBGV("bmc: p1\n");
}

/* Local clock is synchronized to Ebest Table 16 (9.3.5) of the spec */
void s1(PtpClock *ptpClock, const MsgHeader *header, const MsgAnnounce *announce)
{
	bool isFromCurrentParent;

	DBGV("bmc: s1\n");

	/* Current DS */
	ptpClock->currentDS.stepsRemoved = announce->stepsRemoved + 1;

	isFromCurrentParent = isSamePortIdentity(&ptpClock->parentDS.parentPortIdentity, &header->sourcePortIdentity);

	if (!isFromCurrentParent)
	{
			setFlag(ptpClock->events, MASTER_CLOCK_CHANGED);
	}

	/* Parent DS */
	memcpy(ptpClock->parentDS.parentPortIdentity.clockIdentity, header->sourcePortIdentity.clockIdentity, CLOCK_IDENTITY_LENGTH);
	ptpClock->parentDS.parentPortIdentity.portNumber = header->sourcePortIdentity.portNumber;
	memcpy(ptpClock->parentDS.grandmasterIdentity, announce->grandmasterIdentity, CLOCK_IDENTITY_LENGTH);
	ptpClock->parentDS.grandmasterClockQuality.clockAccuracy = announce->grandmasterClockQuality.clockAccuracy;
	ptpClock->parentDS.grandmasterClockQuality.clockClass = announce->grandmasterClockQuality.clockClass;
	ptpClock->parentDS.grandmasterClockQuality.offsetScaledLogVariance = announce->grandmasterClockQuality.offsetScaledLogVariance;
	ptpClock->parentDS.grandmasterPriority1 = announce->grandmasterPriority1;
	ptpClock->parentDS.grandmasterPriority2 = announce->grandmasterPriority2;

	/* Timeproperties DS */
	ptpClock->timePropertiesDS.currentUtcOffset = announce->currentUtcOffset;
	ptpClock->timePropertiesDS.currentUtcOffsetValid = getFlag(header->flagField[1], FLAG1_UTC_OFFSET_VALID);
	ptpClock->timePropertiesDS.leap59 = getFlag(header->flagField[1], FLAG1_LEAP59);
	ptpClock->timePropertiesDS.leap61 = getFlag(header->flagField[1], FLAG1_LEAP61);
	ptpClock->timePropertiesDS.timeTraceable = getFlag(header->flagField[1], FLAG1_TIME_TRACEABLE);
	ptpClock->timePropertiesDS.frequencyTraceable = getFlag(header->flagField[1], FLAG1_FREQUENCY_TRACEABLE);
	ptpClock->timePropertiesDS.ptpTimescale = getFlag(header->flagField[1], FLAG1_PTP_TIMESCALE);
	ptpClock->timePropertiesDS.timeSource = announce->timeSource;
}

/**
 * \brief Copy local data set into header and announce message. 9.3.4 table 12
 */
void copyD0(MsgHeader *header, MsgAnnounce *announce, PtpClock *ptpClock)
{
	announce->grandmasterPriority1 = ptpClock->defaultDS.priority1;
	memcpy(announce->grandmasterIdentity, ptpClock->defaultDS.clockIdentity, CLOCK_IDENTITY_LENGTH);
	announce->grandmasterClockQuality.clockClass = ptpClock->defaultDS.clockQuality.clockClass;
	announce->grandmasterClockQuality.clockAccuracy = ptpClock->defaultDS.clockQuality.clockAccuracy;
	announce->grandmasterClockQuality.offsetScaledLogVariance = ptpClock->defaultDS.clockQuality.offsetScaledLogVariance;
	announce->grandmasterPriority2 = ptpClock->defaultDS.priority2;
	announce->stepsRemoved = 0;
	memcpy(header->sourcePortIdentity.clockIdentity, ptpClock->defaultDS.clockIdentity, CLOCK_IDENTITY_LENGTH);
}


#define A_better_then_B 1
#define B_better_then_A -1
#define A_better_by_topology_then_B 1
#define B_better_by_topology_then_A -1
#define ERROR_1 0
#define ERROR_2 -0


#define COMPARE_AB_RETURN_BETTER(cond, msg)                             \
	if ((announceA->cond) > (announceB->cond)) {                           \
		DBGVV("bmcDataSetComparison: " msg ": B better then A\n");          \
		return B_better_then_A;                                             \
	}                                                                     \
	if ((announceB->cond) > (announceA->cond)) {                           \
		DBGVV("bmcDataSetComparison: " msg ": A better then B\n");          \
		return A_better_then_B;                                             \
	}                                                                     \

/* Data set comparison bewteen two foreign masters (9.3.4 fig 27) return similar to memcmp() */
int8_t bmcDataSetComparison(MsgHeader *headerA, MsgAnnounce *announceA,
															MsgHeader *headerB, MsgAnnounce *announceB, PtpClock *ptpClock)
{
	int grandmasterIdentityComp;
	short comp = 0;

	DBGV("bmcDataSetComparison\n");
	/* Identity comparison */

	/* GM identity of A == GM identity of B */
	/* TODO: zkontrolovat memcmp, co vraci za vysledky !*/
	grandmasterIdentityComp = memcmp(announceA->grandmasterIdentity, announceB->grandmasterIdentity, CLOCK_IDENTITY_LENGTH);

	if (0 != grandmasterIdentityComp)
	{
		/* Algoritgm part 1 - Figure 27 */
		COMPARE_AB_RETURN_BETTER(grandmasterPriority1,"grandmaster.Priority1");
		COMPARE_AB_RETURN_BETTER(grandmasterClockQuality.clockClass,"grandmaster.clockClass");
		COMPARE_AB_RETURN_BETTER(grandmasterClockQuality.clockAccuracy,"grandmaster.clockAccuracy");
		COMPARE_AB_RETURN_BETTER(grandmasterClockQuality.offsetScaledLogVariance,"grandmaster.Variance");
		COMPARE_AB_RETURN_BETTER(grandmasterPriority2,"grandmaster.Priority2");

		if (grandmasterIdentityComp > 0)
		{
			DBGVV("bmcDataSetComparison: grandmaster.Identity: B better then A\n");
			return B_better_then_A;
		}
		else if (grandmasterIdentityComp < 0)
		{
			DBGVV("bmcDataSetComparison: grandmaster.Identity: A better then B\n");
			return A_better_then_B;
		}
	}

	/* Algoritgm part 2 - Figure 28 */
	if ((announceA->stepsRemoved) > (announceB->stepsRemoved + 1))
	{
		DBGVV("bmcDataSetComparison: stepsRemoved: B better then A\n");
		return B_better_then_A;
	}

	if ((announceB->stepsRemoved) > (announceA->stepsRemoved + 1))
	{
		DBGVV("bmcDataSetComparison: stepsRemoved: A better then B\n");
		return A_better_then_B;
	}

	if ((announceA->stepsRemoved) > (announceB->stepsRemoved))
	{
		comp = memcmp(headerA->sourcePortIdentity.clockIdentity, ptpClock->portDS.portIdentity.clockIdentity, CLOCK_IDENTITY_LENGTH);

		if (comp > 0)
		{
			/* reciever < sender */
			DBGVV("bmcDataSetComparison: PortIdentity: B better then A\n");
			return B_better_then_A;
		}
		else if (comp < 0)
		{
			/* reciever > sender */
			DBGVV("bmcDataSetComparison: PortIdentity: B better by topology then A\n");
			return B_better_by_topology_then_A;
		}
		else
		{
			DBGVV("bmcDataSetComparison: ERROR 1\n");
			return ERROR_1;
		}
	}
	else if ((announceA->stepsRemoved) < (announceB->stepsRemoved))
	{
		comp = memcmp(headerB->sourcePortIdentity.clockIdentity, ptpClock->portDS.portIdentity.clockIdentity, CLOCK_IDENTITY_LENGTH);
		if (comp > 0)
		{
			/* reciever < sender */
			DBGVV("bmcDataSetComparison: PortIdentity: A better then B\n");
			return A_better_then_B;
		}
		else if (comp < 0)
		{
			/* reciever > sender */
			DBGVV("bmcDataSetComparison: PortIdentity: A better by topology then B\n");
			return A_better_by_topology_then_B;
		}
		else
		{
			DBGV("bmcDataSetComparison: ERROR 1\n");
			return ERROR_1;
		}
	}

	comp = memcmp(headerA->sourcePortIdentity.clockIdentity, headerB->sourcePortIdentity.clockIdentity, CLOCK_IDENTITY_LENGTH);
	if (comp > 0)
	{
		/* A > B */
		DBGVV("bmcDataSetComparison: sourcePortIdentity: B better by topology then A\n");
		return B_better_by_topology_then_A;
	}
	else if (comp < 0)
	{
		/* B > A */
		DBGVV("bmcDataSetComparison: sourcePortIdentity: A better by topology then B\n");
		return A_better_by_topology_then_B;
	}

	/* compare port numbers of recievers of A and B - same as we have only one port */
	DBGV("bmcDataSetComparison: ERROR 2\n");
	return ERROR_2;
}

/* State decision algorithm 9.3.3 Fig 26 */
uint8_t bmcStateDecision(MsgHeader *header, MsgAnnounce *announce, PtpClock *ptpClock)
{
	int comp;

	if ((!ptpClock->foreignMasterDS.count) && (ptpClock->portDS.portState == PTP_LISTENING))
	{
		return PTP_LISTENING;
	}

	copyD0(&ptpClock->msgTmpHeader, &ptpClock->msgTmp.announce, ptpClock);

	comp = bmcDataSetComparison(&ptpClock->msgTmpHeader, &ptpClock->msgTmp.announce, header, announce, ptpClock);

	DBGV("bmcStateDecision: %d\n", comp);

	if (ptpClock->defaultDS.clockQuality.clockClass < 128)
	{
		if (A_better_then_B == comp)
		{
			m1(ptpClock);  /* M1 */
			return PTP_MASTER;
		}
		else
		{
			p1(ptpClock);
			return PTP_PASSIVE;
		}
	}
	else
	{
		if (A_better_then_B == comp)
		{
			m2(ptpClock); /* M2 */
			return PTP_MASTER;
		}
		else
		{
			s1(ptpClock, header, announce);
			return PTP_SLAVE;
		}
	}
}



uint8_t bmc(PtpClock *ptpClock)
{
	int16_t i, best;

	/* Starting from i = 1, not necessery to test record[i = 0] against record[best = 0] -> they are the same */
	for (i = 1, best = 0; i < ptpClock->foreignMasterDS.count; i++)
	{
		if ((bmcDataSetComparison(&ptpClock->foreignMasterDS.records[i].header, &ptpClock->foreignMasterDS.records[i].announce,
															&ptpClock->foreignMasterDS.records[best].header, &ptpClock->foreignMasterDS.records[best].announce, ptpClock)) < 0)
		{
			best = i;
		}
	}

	DBGV("bmc: best record %d\n", best);
	ptpClock->foreignMasterDS.best = best;

	return bmcStateDecision(&ptpClock->foreignMasterDS.records[best].header, &ptpClock->foreignMasterDS.records[best].announce, ptpClock);
}


